package com.cndw.kungfu.socket;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

import com.cndw.commons.util.BlankUtils;

import flex.messaging.io.ClassAliasRegistry;

/**
 * 客户端
 * 
 * 
 * @author <a href="mailto:whz-work@163.com">汪洪州</a>
 * 
 * @since 1.0 Create on 2011-5-18
 */
public class Client implements Runnable {

	private Integer serverId;
	private Channel channel; // 客户端通道
	private InetSocketAddress address;
	private byte connectStatus = CONNECTING; // 连接状态
	private byte connectFailCounts = 0; // 连接失败次数

	private static ClientBootstrap bootstrap;
	private static final byte CONNECTING = 0; // 连接中
	private static final byte CONNECTED = 1; // 已连接
	private static final byte CONNECTTIMEOUT = 2; // 放弃连接
	private static final byte CONNECT_MAX_FAIL_COUNTS = 5; // 连续失败最多次数
	private static final Map<Integer, Client> allClients = new ConcurrentHashMap<Integer, Client>();
	private static final ChannelPipelineFactory PIPELINE_FACTORY = new ClientPipelineFactory();
	private static final ClientSocketChannelFactory CHANNEL_FACTORY = new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool());

	/**
	 * AMF 映射
	 */
	static {
		bootstrap = new ClientBootstrap(CHANNEL_FACTORY);
		bootstrap.setOption("tcpNoDelay", Boolean.valueOf(true));
		bootstrap.setOption("keepAlive", Boolean.valueOf(true));
		bootstrap.setPipelineFactory(PIPELINE_FACTORY);

		ClassAliasRegistry.getRegistry().registerAlias("com.cndw.rpg.framework.ResponseMessage", "com.cndw.kungfu.socket.SocketResponse");
		ClassAliasRegistry.getRegistry().registerAlias("com.cndw.kungfu.domain.Player", "com.cndw.kungfu.socket.vo.Player");
	}

	/**
	 * 私有构造函数
	 * 
	 * @param serverId
	 * @param host
	 * @param port
	 */
	private Client(Integer serverId, String host, int port) {
		this.serverId = serverId;
		this.address = new InetSocketAddress(host, port);
		allClients.put(getServerId(), this);
	}

	/**
	 * 发送请求
	 * 
	 * @param request
	 * @return
	 */
	public static SocketResponse sendRequest(SocketRequest request) {

		SocketResponse response = new SocketResponse(); // 响应对象

		Client client = getClient(request.getServerId());
		if (!BlankUtils.isBlank(client)) {

			// 如果放弃连接时有请求，立即连接一次
			if (!client.isConnectTimeout()) {
				client.connectStatus = CONNECTING;
				client.connectFailCounts = 0;
				client.connect();
			}

			Channel channel = client.getChannel();
			if (!BlankUtils.isBlank(channel)) {

				ClientHandler.responsies.put(request.getRequestId(), response);
				channel.write(request.serialize()); // 发送请求

				synchronized (response) {
					try {
						response.wait(15000); // 阻塞等待响应，15秒未得到响应放弃等待
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				// 是否为放弃响应，如果是移除Handler中的响应对象
				if (!response.isResponsed()) {
					ClientHandler.responsies.remove(request.getRequestId());
				}
			}
		}
		return response;
	}

	/**
	 * 获取客户端
	 * 
	 * @param serverId
	 * @return
	 */
	public static Client getClient(Integer serverId) {
		return allClients.get(serverId);
	}

	/**
	 * 连续失败放弃连接之后，调用该方法继续连接
	 * 
	 * @param serverId
	 */
	public static void reconnect(Integer serverId) {
		if (contains(serverId)) {
			Client client = allClients.get(serverId);
			if (client.isConnectTimeout()) {
				client.connectStatus = CONNECTING;
				client.connectFailCounts = 0;
				client.connect();
			}
		}
	}

	/**
	 * 启动客户端
	 * 
	 * @param serverId
	 * @param host
	 * @param port
	 */
	public static void start(Integer serverId, String host, int port) {
		start(new Client(serverId, host, port));
	}

	/**
	 * 启动客户端
	 * 
	 * @param client
	 */
	private static void start(Client client) {
		Thread thread = new Thread(client);
		thread.setDaemon(true);
		thread.start();
	}

	/**
	 * 判断是否包含客户端
	 * 
	 * @param serverId
	 * @return
	 */
	private static boolean contains(Integer serverId) {
		return allClients.containsKey(serverId);
	}

	@Override
	public void run() {
		while (true) {

			if (isConnectTimeout()) {
				try {
					TimeUnit.MINUTES.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			connect();
		}
	}

	/**
	 * 连接
	 */
	private synchronized void connect() {
		ChannelFuture connectFuture = bootstrap.connect(address);
		connectFuture.awaitUninterruptibly();
		if (connectFuture.isSuccess()) {

			connectStatus = CONNECTED;
			channel = connectFuture.getChannel();
			ChannelFuture closeFuture = channel.getCloseFuture();
			closeFuture.awaitUninterruptibly();

			if (closeFuture.isSuccess()) {
				connectStatus = CONNECTING;
				connectFailCounts = 0;
				allClients.remove(getServerId());
			}

		} else {
			connectFailCounts++;
			if (connectFailCounts % CONNECT_MAX_FAIL_COUNTS == 0) {
				connectStatus = CONNECTTIMEOUT; // 连续连接失败指定次数放弃连接
			}
		}
	}

	/**
	 * 是否连接中状态
	 * 
	 * @return
	 */
	public boolean isConnecting() {
		return connectStatus == CONNECTING;
	}

	/**
	 * 是否已连接
	 * 
	 * @return
	 */
	public boolean isConnected() {
		return connectStatus == CONNECTED;
	}

	/**
	 * 是否放弃连接
	 * 
	 * @return
	 */
	public boolean isConnectTimeout() {
		return connectStatus == CONNECTTIMEOUT;
	}

	public int getPort() {
		return address.getPort();
	}

	public String getHost() {
		return address.getHostName();
	}

	public Integer getServerId() {
		return serverId;
	}

	public Channel getChannel() {
		return channel;
	}

	public byte getConnectStatus() {
		return connectStatus;
	}

	public byte getConnectFailCounts() {
		return connectFailCounts;
	}
}
